/*
 * create by 陈云亮(shiny_vc@163.com)
 */
(function (mod) {
    if (typeof exports == "object" && typeof module == "object")
        mod(require("../../lib/codemirror"));
    else if (typeof define == "function" && define.amd)
        define(["../../lib/codemirror"], mod);
    else
        mod(CodeMirror);
})(function (CodeMirror) {
    var Pos = CodeMirror.Pos;

    function forEach(arr, f) {
        for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
    }

    function arrayContains(arr, item) {
        if (!Array.prototype.indexOf) {
            var i = arr.length;
            while (i--) {
                if (arr[i] === item) {
                    return true;
                }
            }
            return false;
        }
        return arr.indexOf(item) != -1;
    }

    function scriptHint(editor, getToken, options) {
        var keywords = CodeMirror.epKeywords;
        var cur = editor.getCursor(), token = getToken(editor, cur);
        if (/\b(?:string|comment)\b/.test(token.type)) return;
        token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;

        if (!/^[\w$_]*$/.test(token.string)) {
            token = {
                start: cur.ch, end: cur.ch, string: "", state: token.state,
                type: token.string == "." ? "property" : null
            };
        } else if (token.end > cur.ch) {
            token.end = cur.ch;
            token.string = token.string.slice(0, cur.ch - token.start);
        }

        var tprop = token;
        while (tprop.type == "property") {
            tprop = getToken(editor, Pos(cur.line, tprop.start));
            if (tprop.string != ".") return;
            tprop = getToken(editor, Pos(cur.line, tprop.start));
            if (!context) var context = [];
            context.push(tprop);
        }
        return {
            list: getCompletions(token, context, keywords, options),
            from: Pos(cur.line, token.start),
            to: Pos(cur.line, token.end)
        };
    }

    function epscriptHint(editor, options) {
        return scriptHint(editor,
            function (e, cur) {
                return e.getTokenAt(cur);
            },
            options);
    };
    CodeMirror.registerHelper("hint", "epscript", epscriptHint);

    var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
        "toUpperCase toLowerCase split concat match replace search").split(" ");
    var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
        "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
    var funcProps = "prototype apply call bind".split(" ");
    //var epKeywords = ("break case catch continue default delete do else false finally for function " +
    //    "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
    // var globals = ["debug(obj)",
    //     "cache(name,value)",
    //     "exists(name)",
    //     "print(obj)",
    //     "createVariable(name,type,scope)",
    //     "setValue(name,value)",
    //     "getValue(name)",
    //     "get$(name)",
    //     "set$(name,value)",
    //     "get0$(name)",
    //     "set0$(name,value)",
    //     "list$(name,value)",
    //     "load(sql,expr)",
    //     "load(layer,expr)",
    //     "load(layer,taskId,expr)",
    //     "getSerial(name,len)",
    //     "resetSerial(name)",
    //     "deleteSerial(name)",
    //     "invoke(str,args)",
    //     "isEmpty(obj)",
    //     "isDefine(name)",
    //     "isEquals(src,target)",
    //     "go(taskId,code)",
    //     "getObject(name)",
    //     "i18n(name)",
    //     "#include",
    //     "#app",
    //     "#db",
    //     "#dt",
    //     "#util",
    //     "#bpm",
    //     "parent"]parent
    var epMethods = {
        "#db": ["init(tableId)",
            "execute(code)",
            "execute(code,isAutoCommit)",
            "selectObject(sql)",
            "selectObject(dsId,sql)",
            "selectOne(sql)",
            "selectOne(dsId,sql)",
            "selectList(sql)",
            "selectList(dsId,sql)",
            "transfer()",
            "transfer(tableId,expr)",
            "transfer(tableId,expr,isAutoCommit)",
            "selectMapping(sql)",
            "selectMapping(dsId,expr)",
            "update(sql)",
            "update(dsId,sql)",
            "update(dsId,sql,isAutoCommit)",
            "getEntity(id)",
            "beginTx()",
            "commitTx()",
            "rollbackTx()",
            "closeTx()",
            "lock(tableId,keys)",
            "unlock(tableId,keys)"],
        "#util": ["fromXml(content)",
            "fromSwift(content)",
            "fromJson(content)",
            "toJson(args",
            "setTagValue(field,tag,pos,len)",
            "replace(template)",
            "toFile(data,file)",
            "fromFile(file)",
            "password(str)",
            "encode(str)",
            "decode(str)",
            "toInt(obj)",
            "toLong(obj)",
            "toDouble(obj)",
            "toStr(obj)",
            "toStr(obj,type)",
            "format(obj,pattern)",
            "add(v1,v2)",
            "sub(v1,v2)",
            "mul(v1,v2)",
            "div(v1,v2)",
            "div(v1,v2,scale)",
            "round(v,scale)",
            "amount2Cn(money)",
            "rightPad(obj,size)",
            "rightPad(obj,size,char)",
            "leftPad(obj,size)",
            "leftPad(obj,size,char)",
            "substr(str,start)",
            "substr(str,start,end)",
            "left(str,len)",
            "right(str,len)",
            "substrBetween(str,open,close)",
            "substrAfterLast(str,separator)",
            "substrBefore(str,separator)",
            "substrBeforeLast(str,separator)",
            "substrAfter(str,separator)",
            "substrBetween(str,tag)",
            "substrsBetween(str,open,close)",
            "repeat(str,repeat)",
            "repeat(str,separator,repeat)",
            "isMatch(str,regexp)",
            "concat(data)",
            "concat(data,separator)",
            "UUID()",
            "random(count)",
            "randomAscii(count)",
            "randomAlphabetic(count)",
            "randomAlphanumeric(count)",
            "randomNumeric(count)",
            "encodeBase64(bytes)",
            "decodeBase64(str)",
            "toObject(bytes)",
            "applyAll(name,value)"],
        "#dt": ["isBizDate(date)",
            "isBizDate(date,ccy)",
            "getBizDate(days)",
            "getBizDate(date,days)",
            "getBizDate(ccy,days)",
            "getBizDate(ccy,date,days)",
            "getNextBizDate()",
            "getNextBizDate(date)",
            "getNextBizDate(ccy,date)",
            "getBizDays(from,to)",
            "format(date)",
            "format(date,pattern)",
            "toDate(str)",
            "toDate(str,pattern)",
            "toDay(date)",
            "getHours(first,second)",
            "getDays(first,second)",
            "getMonths(first,second)",
            "addDays(days)",
            "addDays(date,days)",
            "addMonths(months)",
            "addMonths(date,months)",
            "addYears(years)",
            "addYears(date,years)",
            "getMaxDayInMonth(year,month)",
            "isSameMonth(first,second)",
            "getRemainingDays(date)",
            "getYear()",
            "getYear(date)",
            "getMonth()",
            "getMonth(date)",
            "getDay()",
            "getDay(date)",
            "getDayOfYear()",
            "getDayOfYear(date)",
            "getDayOfWeek()",
            "getDayOfWeek(date)",
            "getWeekOfYear()",
            "getWeekOfYear(date)",
            "getWeekOfMonth()",
            "getWeekOfMonth(date)",
            "getMaxDayInMonth()",
            "getMaxDayInMonth(date)"],
        "#app": ["clearUser()",
            "lock()",
            "unlock()",
            "refresh(listId)",
            "poll(type,msgid,users)",
            "offer(type,title,task,users)",
            "offerGroup(type,title,task,groupid)",
            "createBean(type)",
            "publish(title,content,users)",
            "publishGroup(title,content,groupid)",
            "publishGroup(title,content,groupid,priority)",
            "chat(text,users)",
            "chatGroup(text,groupid)",
            "getSUBS(orgs)",
            "getSubs(orgs)",
            "getSUB(orgs)",
            "getSub(orgs)",
            "sendMail(bean)",
            "sendMail(to,subject,msg)",
            "getOnlineUser()"],
        "#bpm": ["getInstanceTask(processId)",
            "startAndExecute(processId)",
            "execute(taskId)",
            "createCCOrder(orderId,actorIds)",
            "updateCCStatus(orderId,actorIds)",
            "take(taskId)",
            "withdraw(taskId)",
            "executeAndJumpTask(taskId,nodeName)",
            "reject(taskId)",
            "resignTo(taskId,actorIds)",
            "resignTo(taskId,performType,actorIds)",
            "addTaskActor(taskId,actorIds)",
            "addTaskActor(taskId,performType,actorIds)",
            "removeTaskActor(taskId,actorIds)",
            "terminate(orderId)",
            "getNodes(processId)",
            "getSub(orgs)"]
    };

    function getCompletions(token, context, keywords, options) {
        var found = [], start = token.string, global = options && options.globalScope;

        function maybeAdd(str) {
            if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
        }

        function gatherCompletions(obj) {
            if (typeof obj == "string") forEach(stringProps, maybeAdd);
            else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
            else if (obj instanceof Function) forEach(funcProps, maybeAdd);
            for (var name in obj) maybeAdd(name);
        }

        if (context && context.length) {
            var obj = context.pop(), base;
            if (obj.type && obj.type.indexOf("variable") === 0) {
                base = epMethods[obj.string];
                if (base)
                    return base;
                if (options && options.additionalContext)
                    base = options.additionalContext[obj.string];
                if (global && (!options || options.useGlobalScope !== false))
                    base = base || global[obj.string];
            } else if (obj.type == "string") {
                base = "";
            } else if (obj.type == "atom") {
                base = 1;
            }
            while (base != null && context.length)
                base = base[context.pop().string];
            if (base != null) gatherCompletions(base);
        } else {
            for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
            for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
            if (!options || options.useGlobalScope !== false)
                gatherCompletions(global);
            //forEach(globals, maybeAdd);
            forEach(keywords, maybeAdd);
        }
        return found;
    }
});
